home *** CD-ROM | disk | FTP | other *** search
/ CU Amiga Super CD-ROM 25 / CU Amiga Magazine's Super CD-ROM 25 (1998)(EMAP Images)(GB)(Track 1 of 2)[!][issue 1998-08].iso / CUCD / Programming / QuakeTools / src / libqbuild / portals.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-06-11  |  16.5 KB  |  640 lines

  1. #define    LIBQBUILD_CORE
  2. #include "../include/libqbuild.h"
  3.  
  4. struct node outside_node;    // 76                           // portals outside the world face this
  5.  
  6. FILE *pf;            // 4
  7. int num_visportals;        // 4
  8. int num_visleafs;        // 4                           // leafs the player can be in
  9. struct visportal *portals;
  10. struct visleaf **leafs;
  11.  
  12. //=============================================================================
  13.  
  14. /*
  15.  * =============
  16.  * AddPortalToNodes
  17.  * =============
  18.  */
  19. void AddPortalToNodes(register struct portal * p, register struct node * front, register struct node * back)
  20. {
  21.   if (p->nodes[0] || p->nodes[1])
  22.     Error("AddPortalToNode: allready included");
  23.  
  24.   p->nodes[0] = front;
  25.   p->next[0] = front->portals;
  26.   front->portals = p;
  27.  
  28.   p->nodes[1] = back;
  29.   p->next[1] = back->portals;
  30.   back->portals = p;
  31. }
  32.  
  33. /*
  34.  * =============
  35.  * RemovePortalFromNode
  36.  * =============
  37.  */
  38. void RemovePortalFromNode(register struct portal * portal, register struct node * l)
  39. {
  40.   struct portal **pp, *t;
  41.  
  42. // remove reference to the current portal
  43.   pp = &l->portals;
  44.   while (1) {
  45.     t = *pp;
  46.     if (!t)
  47.       Error("RemovePortalFromNode: portal not in leaf");
  48.  
  49.     if (t == portal)
  50.       break;
  51.  
  52.     if (t->nodes[0] == l)
  53.       pp = &t->next[0];
  54.     else if (t->nodes[1] == l)
  55.       pp = &t->next[1];
  56.     else
  57.       Error("RemovePortalFromNode: portal not bounding leaf");
  58.   }
  59.  
  60.   if (portal->nodes[0] == l) {
  61.     *pp = portal->next[0];
  62.     portal->nodes[0] = NULL;
  63.   }
  64.   else if (portal->nodes[1] == l) {
  65.     *pp = portal->next[1];
  66.     portal->nodes[1] = NULL;
  67.   }
  68. }
  69.  
  70. //============================================================================
  71.  
  72. void PrintPortal(register struct portal * p)
  73. {
  74.   int i;
  75.   struct winding *w;
  76.  
  77.   w = p->winding;
  78.   for (i = 0; i < w->numpoints; i++)
  79.     mprintf("( %g %g %g )\n", w->points[i][0], w->points[i][1], w->points[i][2]);
  80. }
  81.  
  82. /*
  83.  * ================
  84.  * MakeHeadnodePortals
  85.  * 
  86.  * The created portals will face the global outside_node
  87.  * ================
  88.  */
  89. void MakeHeadnodePortals(__memBase, register struct node * node)
  90. {
  91.   vec3_t bounds[2];
  92.   short int i, j, n;
  93.   struct portal *p, *portals[6];
  94.   struct plane bplanes[6], *pl;
  95.   int side;
  96.  
  97.   Draw_ClearWindow();
  98.  
  99. // pad with some space so there will never be null volume leafs
  100.   for (i = 0; i < 3; i++) {
  101.     bounds[0][i] = brushset->mins[i] - SIDESPACE;
  102.     bounds[1][i] = brushset->maxs[i] + SIDESPACE;
  103.   }
  104.  
  105.   outside_node.contents = CONTENTS_SOLID;
  106.   outside_node.portals = NULL;
  107.  
  108.   for (i = 0; i < 3; i++)
  109.     for (j = 0; j < 2; j++) {
  110.       n = j * 3 + i;
  111.  
  112.       p = AllocPortal();
  113.       portals[n] = p;
  114.  
  115.       pl = &bplanes[n];
  116.       memset(pl, 0, sizeof(*pl));
  117.       if (j) {
  118.     pl->normal[i] = -1;
  119.     pl->dist = -bounds[j][i];
  120.       }
  121.       else {
  122.     pl->normal[i] = 1;
  123.     pl->dist = bounds[j][i];
  124.       }
  125.       p->planenum = FindPlane(bspMem, pl, &side);
  126.  
  127.       p->winding = BaseWindingForPlane(pl);
  128. /** mprintf("----- add portal to node ----\n"); **/
  129.       if (side)
  130.     AddPortalToNodes(p, &outside_node, node);
  131.       else
  132.     AddPortalToNodes(p, node, &outside_node);
  133.     }
  134.  
  135. // clip the basewindings by all the other planes
  136.   for (i = 0; i < 6; i++) {
  137.     for (j = 0; j < 6; j++) {
  138.       if (j == i)
  139.     continue;
  140.       portals[i]->winding = ClipWinding(portals[i]->winding, &bplanes[j], TRUE);
  141.     }
  142.   }
  143. }
  144.  
  145. //============================================================================
  146.  
  147. void CheckLeafPortalConsistancy(__memBase, register struct node * node)
  148. {
  149.   int side, side2;
  150.   struct portal *p, *p2;
  151.   struct plane plane, plane2;
  152.   int i;
  153.   struct winding *w;
  154.   float dist;
  155.  
  156.   side = side2 = 0;                                   // quiet compiler warning
  157.  
  158.   for (p = node->portals; p; p = p->next[side]) {
  159.     if (p->nodes[0] == node)
  160.       side = 0;
  161.     else if (p->nodes[1] == node)
  162.       side = 1;
  163.     else
  164.       Error("CutNodePortals_r: mislinked portal");
  165.     CheckWindingInNode(p->winding, node);
  166.     CheckWindingArea(p->winding);
  167.  
  168.     // check that the side orders are correct
  169. #ifdef EXHAUSIVE_CHECK
  170.   if(p->planenum >= bspMem->numbrushplanes || p->planenum < 0)
  171.     Error("looking for nonexisting plane %d\n", p->planenum);
  172. #endif
  173.     plane = bspMem->brushplanes[p->planenum];
  174.     PlaneFromWinding(p->winding, &plane2);
  175.  
  176.     for (p2 = node->portals; p2; p2 = p2->next[side2]) {
  177.       if (p2->nodes[0] == node)
  178.     side2 = 0;
  179.       else if (p2->nodes[1] == node)
  180.     side2 = 1;
  181.       else
  182.     Error("CutNodePortals_r: mislinked portal");
  183.       w = p2->winding;
  184.       for (i = 0; i < w->numpoints; i++) {
  185.     dist = DotProduct(w->points[i], plane.normal) - plane.dist;
  186.     if ((side == 0 && dist < -1) || (side == 1 && dist > 1)) {
  187.       eprintf("portal siding direction is wrong\n");
  188.       return;
  189.     }
  190.       }
  191.  
  192.     }
  193.   }
  194. }
  195.  
  196. /*
  197.  * ================
  198.  * CutNodePortals_r
  199.  * 
  200.  * ================
  201.  */
  202. void CutNodePortals_r(__memBase, register struct node * node)
  203. {
  204.   struct plane *plane, clipplane;
  205.   struct node *f, *b, *other_node;
  206.   struct portal *p, *new_portal, *next_portal;
  207.   struct winding *w, *frontwinding, *backwinding;
  208.   int side;
  209.  
  210. /** mprintf("----- cut node portals ----\n"); **/
  211.  
  212. //      CheckLeafPortalConsistancy (node);
  213.  
  214. //
  215.   // seperate the portals on node into it's children      
  216.   //
  217.   if (node->contents) {
  218.     return;                                       // at a leaf, no more dividing
  219.  
  220.   }
  221.  
  222. #ifdef EXHAUSIVE_CHECK
  223.   if(node->planenum >= bspMem->numbrushplanes || node->planenum < 0)
  224.     Error("looking for nonexisting plane %d\n", node->planenum);
  225. #endif
  226.   plane = &bspMem->brushplanes[node->planenum];
  227.  
  228.   f = node->children[0];
  229.   b = node->children[1];
  230.  
  231. //
  232.   // create the new portal by taking the full plane winding for the cutting plane
  233.   // and clipping it by all of the planes from the other portals
  234.   //
  235.   new_portal = AllocPortal();
  236.   new_portal->planenum = node->planenum;
  237.  
  238.   w = BaseWindingForPlane(&bspMem->brushplanes[node->planenum]);
  239.   side = 0;                                       // shut up compiler warning
  240.  
  241.   for (p = node->portals; p; p = p->next[side]) {
  242.     clipplane = bspMem->brushplanes[p->planenum];
  243.     if (p->nodes[0] == node)
  244.       side = 0;
  245.     else if (p->nodes[1] == node) {
  246.       clipplane.dist = -clipplane.dist;
  247.       VectorNegate(clipplane.normal);
  248.       side = 1;
  249.     }
  250.     else
  251.       Error("CutNodePortals_r: mislinked portal");
  252.  
  253.     w = ClipWinding(w, &clipplane, TRUE);
  254.     if (!w) {
  255.       eprintf("CutNodePortals_r: new portal was clipped away\n");
  256.       break;
  257.     }
  258.   }
  259.  
  260.   if (w) {
  261.     // if the plane was not clipped on all sides, there was an error
  262.     new_portal->winding = w;
  263.     AddPortalToNodes(new_portal, f, b);
  264.   }
  265.  
  266. //
  267.   // partition the portals
  268.   //
  269.   for (p = node->portals; p; p = next_portal) {
  270.     if (p->nodes[0] == node)
  271.       side = 0;
  272.     else if (p->nodes[1] == node)
  273.       side = 1;
  274.     else
  275.       Error("CutNodePortals_r: mislinked portal");
  276.     next_portal = p->next[side];
  277.  
  278.     other_node = p->nodes[!side];
  279.     RemovePortalFromNode(p, p->nodes[0]);
  280.     RemovePortalFromNode(p, p->nodes[1]);
  281.  
  282. //
  283.     // cut the portal into two portals, one on each side of the cut plane
  284.     //
  285.     DivideWinding(p->winding, plane, &frontwinding, &backwinding);
  286.  
  287.     if (!frontwinding) {
  288.       if (side == 0)
  289.     AddPortalToNodes(p, b, other_node);
  290.       else
  291.     AddPortalToNodes(p, other_node, b);
  292.       continue;
  293.     }
  294.     if (!backwinding) {
  295.       if (side == 0)
  296.     AddPortalToNodes(p, f, other_node);
  297.       else
  298.     AddPortalToNodes(p, other_node, f);
  299.       continue;
  300.     }
  301.  
  302.     // the winding is split
  303.     new_portal = AllocPortal();
  304.     *new_portal = *p;
  305.     new_portal->winding = backwinding;
  306.     FreeWinding(p->winding);
  307.     p->winding = frontwinding;
  308.  
  309.     if (side == 0) {
  310.       AddPortalToNodes(p, f, other_node);
  311.       AddPortalToNodes(new_portal, b, other_node);
  312.     }
  313.     else {
  314.       AddPortalToNodes(p, other_node, f);
  315.       AddPortalToNodes(new_portal, other_node, b);
  316.     }
  317.   }
  318.  
  319.   DrawLeaf(f, 1);
  320.   DrawLeaf(b, 2);
  321.  
  322.   CutNodePortals_r(bspMem, f);
  323.   CutNodePortals_r(bspMem, b);
  324.  
  325. }
  326.  
  327. /*
  328.  * ==================
  329.  * PortalizeWorld
  330.  * 
  331.  * Builds the exact polyhedrons for the nodes and leafs
  332.  * ==================
  333.  */
  334. void PortalizeWorld(__memBase, struct node * headnode)
  335. {
  336.   mprintf("----- Portalize ---------\n");
  337.  
  338.   MakeHeadnodePortals(bspMem, headnode);
  339.   CutNodePortals_r(bspMem, headnode);
  340. }
  341.  
  342. /*
  343.  * ==================
  344.  * FreeAllPortals
  345.  * 
  346.  * ==================
  347.  */
  348. void FreeAllPortals(struct node * node)
  349. {
  350.   struct portal *p, *nextp;
  351.  
  352.   if (!node->contents) {
  353.     FreeAllPortals(node->children[0]);
  354.     FreeAllPortals(node->children[1]);
  355.   }
  356.  
  357.   for (p = node->portals; p; p = nextp) {
  358.     if (p->nodes[0] == node)
  359.       nextp = p->next[0];
  360.     else
  361.       nextp = p->next[1];
  362.     RemovePortalFromNode(p, p->nodes[0]);
  363.     RemovePortalFromNode(p, p->nodes[1]);
  364.     FreeWinding(p->winding);
  365.     FreePortal(p);
  366.   }
  367. }
  368.  
  369. /*
  370.  * ==============================================================================
  371.  * 
  372.  * PORTAL FILE GENERATION
  373.  * 
  374.  * ==============================================================================
  375.  */
  376.  
  377. /*
  378. void WriteFloat(register FILE * f, register vec_t v)
  379. {
  380.   if (fabs(v - rint(v)) < 0.001)
  381.     fprintf(f, "%i ", (int)rint(v));
  382.   else
  383.     fprintf(f, "%f ", v);
  384. }
  385.  */
  386.  
  387. void WritePortalFile_r(__memBase, register struct node * node)
  388. {
  389.   int i;
  390.   struct portal *p;
  391.   struct winding *w;
  392.   struct plane *pl, plane2;
  393.  
  394.   if (!node->contents) {
  395.     WritePortalFile_r(bspMem, node->children[0]);
  396.     WritePortalFile_r(bspMem, node->children[1]);
  397.     return;
  398.   }
  399.  
  400.   if (node->contents == CONTENTS_SOLID)
  401.     return;
  402.  
  403.   for (p = node->portals; p;) {
  404.     w = p->winding;
  405. /*
  406.  *  if (w && p->nodes[0] == node
  407.  *    && p->nodes[0]->contents == p->nodes[1]->contents) {
  408.  */
  409.     if (w && p->nodes[0] == node) {
  410.       if (((bspMem->bspOptions & QBSP_WATERVIS) &&
  411.        ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
  412.         (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)))
  413.     || ((bspMem->bspOptions & QBSP_SLIMEVIS) &&
  414.        ((p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_EMPTY) ||
  415.         (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_SLIME)))
  416.     || ((bspMem->bspOptions & QBSP_WATERVIS) && (bspMem->bspOptions & QBSP_SLIMEVIS) &&
  417.        ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_SLIME) ||
  418.         (p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_WATER)))
  419.     || (p->nodes[0]->contents == p->nodes[1]->contents)) {
  420.       // write out to the file
  421.  
  422.       // sometimes planes get turned around when they are very near
  423.       // the changeover point between different axis.  interpret the
  424.       // plane the same way vis will, and flip the side orders if needed
  425. #ifdef EXHAUSIVE_CHECK
  426.   if(p->planenum >= bspMem->numbrushplanes || p->planenum < 0)
  427.     Error("looking for nonexisting plane %d\n", p->planenum);
  428. #endif
  429.       pl = &bspMem->brushplanes[p->planenum];
  430.       PlaneFromWinding(w, &plane2);
  431.       if (DotProduct(pl->normal, plane2.normal) < 0.99) {               // backwards...
  432.  
  433.     fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum);
  434.       }
  435.       else
  436.     fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum);
  437.       for (i = 0; i < w->numpoints; i++) {
  438.     //fprintf(pf, "(");
  439.     //WriteFloat(pf, w->points[i][0]);
  440.     //WriteFloat(pf, w->points[i][1]);
  441.     //WriteFloat(pf, w->points[i][2]);
  442.     //fprintf(pf, ") ");
  443.     fprintf(pf, "( %g %g %g ) ", w->points[i][0], w->points[i][1], w->points[i][2]);
  444.       }
  445.       fprintf(pf, "\n");
  446.     }
  447.     }
  448.  
  449.     if (p->nodes[0] == node)
  450.       p = p->next[0];
  451.     else
  452.       p = p->next[1];
  453.   }
  454.  
  455. }
  456.  
  457. /*
  458.  * ================
  459.  * NumberLeafs_r
  460.  * ================
  461.  */
  462. void NumberLeafs_r(__memBase, register struct node * node)
  463. {
  464.   struct portal *p;
  465.  
  466.   if (!node->contents) {                               // decision node
  467.  
  468.     node->visleafnum = -99;
  469.     NumberLeafs_r(bspMem, node->children[0]);
  470.     NumberLeafs_r(bspMem, node->children[1]);
  471.     return;
  472.   }
  473.  
  474.   Draw_ClearWindow();
  475.   DrawLeaf(node, 1);
  476.  
  477.   if (node->contents == CONTENTS_SOLID) {                       // solid block, viewpoint never inside
  478.  
  479.     node->visleafnum = -1;
  480.     return;
  481.   }
  482.  
  483.   node->visleafnum = num_visleafs++;
  484.  
  485.   for (p = node->portals; p;) {
  486.     if (p->nodes[0] == node)                               // only write out from first leaf
  487.      {
  488. /*
  489.  *    if (p->nodes[0]->contents == p->nodes[1]->contents)
  490.  *    num_visportals++;
  491.  */
  492.       if (((bspMem->bspOptions & QBSP_WATERVIS) &&
  493.        ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
  494.         (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)))
  495.     || ((bspMem->bspOptions & QBSP_SLIMEVIS) &&
  496.        ((p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_EMPTY) ||
  497.         (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_SLIME)))
  498.     || ((bspMem->bspOptions & QBSP_WATERVIS) && (bspMem->bspOptions & QBSP_SLIMEVIS) &&
  499.        ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_SLIME) ||
  500.         (p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_WATER)))
  501.     || (p->nodes[0]->contents == p->nodes[1]->contents)) {
  502.     num_visportals++;
  503.       }
  504.       p = p->next[0];
  505.     }
  506.     else
  507.       p = p->next[1];
  508.   }
  509.  
  510. }
  511.  
  512. /*
  513.  * ================
  514.  * WritePortalfile
  515.  * ================
  516.  */
  517. void WritePortalfile(__memBase, struct node *headnode, char *portfilename)
  518. {
  519. // set the visleafnum field in every leaf and count the total number of portals
  520.   num_visleafs = 0;
  521.   num_visportals = 0;
  522.   NumberLeafs_r(bspMem, headnode);
  523.  
  524. // write the file
  525.   mprintf("    - writing %s\n", portfilename);
  526.   pf = fopen(portfilename, "w");
  527.   if (!pf)
  528.     Error("Error opening %s", portfilename);
  529.  
  530.   fprintf(pf, "%s\n", PORTALFILE);
  531.   fprintf(pf, "%i\n", num_visleafs);
  532.   fprintf(pf, "%i\n", num_visportals);
  533.  
  534.   WritePortalFile_r(bspMem, headnode);
  535.  
  536.   fclose(pf);
  537. }
  538.  
  539. /*
  540.  * ============
  541.  * LoadPortals
  542.  * ============
  543.  */
  544. void LoadPortals(char *prtBuf)
  545. {
  546.   int i, j;
  547.   int read;
  548.   struct visportal *p;
  549.   struct visleaf *l;
  550.   char magic[80];
  551.   int numpoints;
  552.   struct winding *w;
  553.   int leafnums[2];
  554.   struct plane plane;
  555.  
  556.   mprintf("----- LoadPortals -------\n");
  557.  
  558.   if (sscanf(prtBuf, "%79s\n%i\n%i\n%n", magic, &num_visleafs, &num_visportals, &read) != 3)
  559.     Error("LoadPortals: failed to read header");
  560.   prtBuf += read;
  561.   if (strcmp(magic, PORTALFILE))
  562.     Error("LoadPortals: not a portal file");
  563.  
  564. // each file portal is split into two memory portals
  565.   if(!(portals = (struct visportal *)tmalloc(2 * num_visportals * sizeof(struct visportal))))
  566.     Error("LoadPortals: failed to allocate visportal!\n");
  567.   //leafs = (struct visleaf *)tmalloc(num_visleafs * sizeof(struct visleaf));
  568.   if(!(leafs = (struct visleaf **)tmalloc(num_visleafs * sizeof(struct visleaf *))))
  569.     Error("LoadPortals: failed to allocate visleaf!\n");
  570.  
  571.   for (i = 0, p = portals; i < num_visportals; i++) {
  572.     if (sscanf(prtBuf, "%i %i %i %n", &numpoints, &leafnums[0], &leafnums[1], &read) != 3)
  573.       Error("LoadPortals: reading portal %i\n", i);
  574.     if (numpoints > MAX_POINTS_ON_WINDING)
  575.       Error("LoadPortals: portal %i has too many points\n", i);
  576.     if ((unsigned)leafnums[0] > num_visleafs || (unsigned)leafnums[1] > num_visleafs)
  577.       Error("LoadPortals: reading portal %i\n", i);
  578.     prtBuf += read;
  579.  
  580.     w = p->winding = NewWinding(numpoints);
  581.     w->original = TRUE;
  582.     w->numpoints = numpoints;
  583.  
  584.     for (j = 0; j < numpoints; j++) {
  585.       int k;
  586.  
  587.       // scanf into double, then assign to vec_t
  588.       if ((k = sscanf(prtBuf, "( %g %g %g ) %n",&w->points[j][0], &w->points[j][1], &w->points[j][2], &read)) != 3)
  589.     Error("LoadPortals: reading portal %i (%i elements)\n", i, k);
  590.       prtBuf += read;
  591.     }
  592.     sscanf(prtBuf, "\n%n", &read);
  593.     prtBuf += read;
  594.  
  595.     // calc plane
  596.     PlaneFromWinding(w, &plane);
  597.  
  598.     // create forward portal
  599.     //l = &leafs[leafnums[0]];
  600.     if(!(l = leafs[leafnums[0]]))
  601.       l = leafs[leafnums[0]] = AllocLeaf(MAX_PORTALS_ON_LEAF);
  602.     else if (l->numportals == MAX_PORTALS_ON_LEAF)
  603.       Error("Leaf with too many portals");
  604.     l->portals[l->numportals] = p;
  605.     l->numportals++;
  606.  
  607.     p->winding = w;
  608.     VectorNegateTo(plane.normal, p->plane.normal);
  609.     p->plane.dist = -plane.dist;
  610.     p->leaf = leafnums[1];
  611.     p++;
  612.  
  613.     // create backwards portal
  614.     //l = &leafs[leafnums[1]];
  615.     if(!(l = leafs[leafnums[1]]))
  616.       l = leafs[leafnums[1]] = AllocLeaf(MAX_PORTALS_ON_LEAF);
  617.     else if (l->numportals == MAX_PORTALS_ON_LEAF)
  618.       Error("Leaf with too many portals");
  619.     l->portals[l->numportals] = p;
  620.     l->numportals++;
  621.  
  622.     p->winding = w;
  623.     p->plane = plane;
  624.     p->leaf = leafnums[0];
  625.     p++;
  626.     
  627.     mprogress(num_visportals, i + 1);
  628.   }
  629.   
  630.   mprintf("%5i num_visportals\n", num_visportals);
  631.   mprintf("%5i num_visleafs\n", num_visleafs);
  632.  
  633.   for(i = 0; i < num_visleafs; i++) {
  634.     if(leafs[i])
  635.       RecalcLeaf(leafs[i]);
  636.     else
  637.       leafs[i] = AllocLeaf(MAX_PORTALS_ON_LEAF);
  638.   }
  639. }
  640.